'use strict';

const path = require('path');
const fs = require('fs');
const url = require('url');
const mergeWith = require('lodash/mergeWith');
const isArray = require('lodash/isArray');
const uniqBy = require('lodash/uniqBy');

function customizer(objValue, srcValue) {
  if (isArray(objValue) && isArray(srcValue)) {
    let res = objValue.concat(srcValue);
    if (res[0] && res[0].name) {
      res = uniqBy(res, 'name');
    }
    return res;
  }
}

const getCpaasConfig = (pathVar) => {
  if (!pathVar) {
    return null;
  }
  let cpaasConfig = null;
  const cpaasrcJsFile = path.resolve(pathVar, '.cpaasrc.js');
  if (fs.existsSync(cpaasrcJsFile)) {
    const config = require(cpaasrcJsFile);
    cpaasConfig = mergeWith(cpaasConfig, config, customizer);
  }
  const cpaasrcFile = path.resolve(pathVar, '.cpaasrc.json');
  if (fs.existsSync(cpaasrcFile)) {
    const config = require(cpaasrcFile);
    cpaasConfig = mergeWith(cpaasConfig, config, customizer);
  }
  return cpaasConfig;
};

const getCpaasBootConfig = (pathVar) => {
  if (!pathVar) {
    return null;
  }
  let cpaasConfig = getCpaasConfig(pathVar);
  if (!isCpaasRoot(pathVar)) {
    const newPath = findCpaasRootPath(pathVar);
    const rootCpaasConfig = getCpaasConfig(newPath);
    cpaasConfig = mergeWith(rootCpaasConfig, cpaasConfig);
  }
  return cpaasConfig;
};

const isCpaasRoot = (pathVar) => {
  const cpaasConfig = getCpaasConfig(pathVar);
  if (cpaasConfig && cpaasConfig.packages) {
    return true;
  }
  // 判断是否存在 lerna.json, 存在则认为在根目录,否则在子模块或者其他目录
  return fs.existsSync(path.resolve(pathVar, 'lerna.json') && cpaasConfig);
};

/**
 *  AppRoot 目录
 */
const findCpaasRootPath = (currentPath) => {
  let goToPath = './';
  let findCount = 0;
  while (!isCpaasRoot(path.resolve(currentPath, goToPath))) {
    goToPath += '../';
    findCount++;
    if (findCount == 4) {
      return process.env.CPAAS_APP_ROOT_PATH || currentPath;
    }
  }
  return path.resolve(currentPath, goToPath);
};

const pwdIsRoot = isCpaasRoot(process.cwd());

/**
 * 找到某个模块的位置
 * @param {*} moduleName
 */
const resolveCpaasModule = (moduleName) => {
  const modulePackageFilePath = `${moduleName}/package.json`;
  let resultFullPath;
  // 第一种情况， 当前目录的是父工程，子模块安装在 node_modules 下面
  resultFullPath = path.resolve(
    process.cwd(),
    'node_modules',
    modulePackageFilePath
  );
  if (fs.existsSync(resultFullPath)) {
    return resultFullPath;
  }

  // 第二种情况， 当前目录的是子工程，当前安装子模块安装在 node_modules 下面
  resultFullPath = path.resolve(
    process.cwd(),
    '../../node_modules',
    modulePackageFilePath
  );
  if (fs.existsSync(resultFullPath)) {
    return resultFullPath;
  }

  // 第三种情况， 当前目录的是父工程，子模块安装在 lerna 管理的 packages 下面
  resultFullPath = path.resolve(
    process.cwd(),
    'packages',
    modulePackageFilePath
  );
  if (fs.existsSync(resultFullPath)) {
    return resultFullPath;
  }

  // 第四种情况， 当前目录的是子模块，子模块安装在 lerna 管理的 packages 下面
  resultFullPath = path.resolve(
    process.cwd(),
    '../../packages',
    modulePackageFilePath
  );
  if (fs.existsSync(resultFullPath)) {
    return resultFullPath;
  }

  console.log('找不到文件：', modulePackageFilePath);
  process.exit(1);
  return modulePackageFilePath;
};

// Make sure any symlinks in the project folder are resolved:
// https://github.com/facebook/create-react-app/issues/637
//** 当前模块的位置可能是子模块也可能是父模块
const getAppDirectory = (_BUILD_MODULE) => {
  const BUILD_MODULE = _BUILD_MODULE || process.env.BUILD_MODULE;
  if (BUILD_MODULE) {
    const nodePackagePath = resolveCpaasModule(BUILD_MODULE);
    const nodePath = path.dirname(nodePackagePath);
    return fs.realpathSync(nodePath);
  } else {
    return fs.realpathSync(path.resolve(process.cwd()));
  }
};

const appDirectory = getAppDirectory();
const projectDirectory = findCpaasRootPath(appDirectory);

const resolveApp = (relativePath) => path.resolve(appDirectory, relativePath);
const resolveProject = (relativePath) =>
  path.resolve(projectDirectory, relativePath);

const envPublicUrl = process.env.PUBLIC_URL;

function ensureSlash(inputPath, needsSlash) {
  const hasSlash = inputPath.endsWith('/');
  if (hasSlash && !needsSlash) {
    return inputPath.substr(0, inputPath.length - 1);
  } else if (!hasSlash && needsSlash) {
    return `${inputPath}/`;
  } else {
    return inputPath;
  }
}

const getPublicUrl = (appPackageJson) => {
  if (envPublicUrl) {
    return envPublicUrl;
  }
  if (fs.existsSync(appPackageJson)) {
    require(appPackageJson).homepage || '/';
  }
  return '/';
};

// We use `PUBLIC_URL` environment variable or "homepage" field to infer
// "public path" at which the app is served.
// Webpack needs to know it to put the right <script> hrefs into HTML even in
// single-page apps that may serve index.html for nested URLs like /todos/42.
// We can't use a relative path in HTML because we don't want to load something
// like /todos/42/static/js/bundle.7289d.js. We have to know the root.
function getServedPath(appPackageJson) {
  const publicUrl = getPublicUrl(appPackageJson);
  const servedUrl =
    envPublicUrl || (publicUrl ? url.parse(publicUrl).pathname : '/');
  return ensureSlash(servedUrl, true);
}

const moduleFileExtensions = [
  'web.mjs',
  'mjs',
  'web.js',
  'js',
  'web.ts',
  'ts',
  'web.tsx',
  'tsx',
  'json',
  'web.jsx',
  'jsx',
  'vue',
];

// Resolve file paths in the same order as webpack
const resolveModule = (resolveFn, filePath) => {
  const extension = moduleFileExtensions.find((extension) =>
    fs.existsSync(resolveFn(`${filePath}.${extension}`))
  );

  if (extension) {
    return resolveFn(`${filePath}.${extension}`);
  }

  return resolveFn(`${filePath}.js`);
};

let cpaasBoot = null;

try {
  const cpaasConfig = getCpaasBootConfig(appDirectory);
  const cpaasBootPathConfig =
    (cpaasConfig && cpaasConfig.cpaasBoot) || 'cpaas-boot/lib/pathInfo';
  cpaasBoot = require(require.resolve(cpaasBootPathConfig, {
    paths: [appDirectory],
  }));
} catch (e) {
  // console.error(e);
  cpaasBoot = {
    publicPath: resolveProject(`public`),
    rootEntryFile: resolveModule(resolveProject, `src/index`),
    moduleEntryFile: resolveModule(resolveApp, 'src/index'),
    moduleMsEntryFile: resolveModule(resolveApp, 'src/utils/router'),
  };
}

let appHtml = resolveProject(`public/index.html`);
if (!fs.existsSync(appHtml)) {
  appHtml = `${cpaasBoot.publicPath}/index.html`;
}

const isEnvDevelopment = process.env.NODE_ENV === 'development';

let ENV_BUILD_DIST_PATH = process.env.BUILD_DIST_PATH;

if (ENV_BUILD_DIST_PATH && !path.isAbsolute(ENV_BUILD_DIST_PATH)) {
  ENV_BUILD_DIST_PATH = path.resolve(resolveProject('.'), ENV_BUILD_DIST_PATH);
}

const appConfig = fs.existsSync(resolveProject('config'))
  ? resolveProject('config')
  : resolveProject('src/config');

const isDevDll =
  !pwdIsRoot && isEnvDevelopment && process.env.FORCE_PROD_DLL !== 'true';

const appTsConfig = fs.existsSync(resolveApp('tsconfig.json'))
  ? resolveApp('tsconfig.json')
  : resolveProject('tsconfig.json');

// config after eject: we're in ./config/
module.exports = {
  dotenv: resolveProject(`${appConfig}/.env`),
  appDotenv: resolveApp(`${appConfig}/.env`),
  appPath: resolveApp('.'),
  appRootPath: resolveProject('.'),
  appBuild: ENV_BUILD_DIST_PATH || resolveApp('dist'),
  appRootBuild: ENV_BUILD_DIST_PATH || resolveProject(`dist`),
  appParentBuild: path.join(
    ENV_BUILD_DIST_PATH || resolveProject(`dist`),
    'packages/.parent'
  ),
  appPublic: resolveProject(`public`),
  appMsPublic: resolveApp(`public`),
  appHtml,
  appIndexJs: pwdIsRoot ? cpaasBoot.rootEntryFile : cpaasBoot.moduleEntryFile,
  appPackageJson: resolveApp('package.json'),
  appSrc: fs.existsSync(resolveApp('src'))
    ? resolveApp('src')
    : resolveApp('lib'),
  appConfig,
  cpaasBootPublic: cpaasBoot.publicPath,
  appMsIndexJs: cpaasBoot.moduleMsEntryFile,
  appTsConfig,
  yarnLockFile: resolveApp('yarn.lock'),
  testsSetup: resolveModule(resolveApp, 'src/setupTests'),
  proxySetup: resolveModule(resolveProject, 'src/setupProxy'),
  appNodeModules: resolveApp('node_modules'),
  publicUrl: getPublicUrl(resolveApp('package.json')),
  servedPath: getServedPath(resolveApp('package.json')),
  isDevDll,
  appDll: isDevDll
    ? resolveProject(`node_modules/.cache/cpaas-front-dev-dll`)
    : resolveProject(`node_modules/.cache/cpaas-front-prod-dll`),
};

module.exports.moduleFileExtensions = moduleFileExtensions;
module.exports.pwdIsRoot = pwdIsRoot;
module.exports.getAppDirectory = getAppDirectory;
module.exports.getCpaasConfig = getCpaasConfig;
module.exports.resolveModuleMsApp = (filePath) =>
  resolveModule(resolveApp, filePath);
module.exports.getCpaasBootConfig = getCpaasBootConfig;
